/*
 * Copyright 2014, NICTA
 *
 * This software may be distributed and modified according to the terms of
 * the BSD 2-Clause license. Note that NO WARRANTY is provided.
 * See "LICENSE_BSD2.txt" for details.
 *
 * @TAG(NICTA_BSD)
 */

#include <stdarg.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <sos.h>

#include <sel4/sel4.h>

#define MIN(a,b) ((a) < (b) ? (a) : (b))
#define DIVROUND(a,b) (((a) + ((b) - 1)) / (b))

#define verbose 5

int sos_sys_open(const char *path, fmode_t mode) {
    seL4_IPCBuffer *ipc;
    seL4_MessageInfo_t tag;
    seL4_Word *data;
    int len;
    int i;

    if (path == NULL) return -1;

    ipc = seL4_GetIPCBuffer();
    len = strnlen(path, SOS_PATH_MAX);
    // Enforce max path length
    if (len >= SOS_PATH_MAX || len == 0) return -1;

    data = (seL4_Word *) path;
    tag = seL4_MessageInfo_new(0, 0, 0, seL4_MsgMaxLength);
    ipc->msg[0] = SOS_SYSCALL_OPEN;
    ipc->msg[1] = (seL4_Word) mode;
    ipc->msg[2] = len;

    strncpy((char *) (ipc->msg + 3), path, len + 1);
    seL4_Call(SOS_IPC_EP_CAP, tag);

    if (ipc->msg[1]) return -1;
    else return (ipc->msg[0]);
}

int sos_sys_close(int file) {
    seL4_IPCBuffer *buf;
    seL4_MessageInfo_t tag;

    buf = seL4_GetIPCBuffer();
    tag = seL4_MessageInfo_new(0, 0, 0, 2);
    buf->msg[0] = SOS_SYSCALL_CLOSE;
    buf->msg[1] = file;
    seL4_Call(SOS_IPC_EP_CAP, tag);

    if (buf->msg[0]) return -1;
    else return 0;
}

int sos_sys_read(int file, char *buf, size_t nbyte) {
    seL4_IPCBuffer *ipc;
    seL4_MessageInfo_t tag;

    if (nbyte == 0) return 0;
    if (buf == NULL) return -1;
    if (file < 0) return -1;

    ipc = seL4_GetIPCBuffer();
    tag = seL4_MessageInfo_new(0, 0, 0, 4);
    ipc->msg[0] = SOS_SYSCALL_READ;
    ipc->msg[1] = (seL4_Word) file;
    ipc->msg[2] = (seL4_Word) buf;
    ipc->msg[3] = (seL4_Word) nbyte;
    seL4_Call(SOS_IPC_EP_CAP, tag);

    if (ipc->msg[1]) return -1;
    else return (ipc->msg[0]);
}


static size_t sos_debug_print(const void *vData, size_t count) {
    size_t i;
    const char *realdata = vData;
    for (i = 0; i < count; i++)
        seL4_DebugPutChar(realdata[i]);
    return count;
}

int sos_sys_write(int file, const char *buf, size_t nbyte) {
    seL4_IPCBuffer *ipc;
    seL4_MessageInfo_t tag;
    int ret;
    int err;


    if (nbyte == 0) return 0;
    if (buf == NULL) return -1;
    if (file < 0) return -1;


    ipc = seL4_GetIPCBuffer();
    tag = seL4_MessageInfo_new(0, 0, 0, 4);
    ipc->msg[0] = SOS_SYSCALL_WRITE;
    ipc->msg[1] = (seL4_Word) file;
    ipc->msg[2] = (seL4_Word) buf;
    ipc->msg[3] = (seL4_Word) nbyte;
    seL4_Call(SOS_IPC_EP_CAP, tag);

    ret = (int) ipc->msg[0];
    if (ret > nbyte) ret = nbyte;
    err = (int) ipc->msg[1];

    if (err != 0) return -1;
    else return ret;
}

int sos_stat(const char *path, sos_stat_t *buf) {
    seL4_IPCBuffer *ipc;
    seL4_MessageInfo_t tag;
    seL4_Word *data;
    int len;
    int i;

    if (path == NULL) return -1;

    ipc = seL4_GetIPCBuffer();
    len = strnlen(path, SOS_PATH_MAX);
    data = (seL4_Word *) path;

    tag = seL4_MessageInfo_new(0, 0, 0, seL4_MsgMaxLength);
    ipc->msg[0] = SOS_SYSCALL_STAT;
    ipc->msg[1] = (int) buf;
    ipc->msg[2] = len;

    strncpy(ipc->msg + 3, path, len + 1);
    seL4_Call(SOS_IPC_EP_CAP, tag);

    if ((ipc->msg[0]) != 0) {
        return -1;
    } else {
        buf->st_type  = ipc->msg[1];
        buf->st_fmode = ipc->msg[2];
        buf->st_size  = ipc->msg[3];
        buf->st_ctime = (long) ipc->msg[4];
        buf->st_atime = (long) ipc->msg[5];
        return 0;
    }

}

int sos_getdirent(int pos, char *name, size_t nbyte) {
    seL4_IPCBuffer *ipc;
    seL4_MessageInfo_t tag;

    ipc = seL4_GetIPCBuffer();
    tag = seL4_MessageInfo_new(0, 0, 0, 3);
    ipc->msg[0] = SOS_SYSCALL_GETDIRENT;
    ipc->msg[1] = (seL4_Word) pos;
    ipc->msg[2] = (seL4_Word) nbyte;
    seL4_Call(SOS_IPC_EP_CAP, tag);

    if (ipc->msg[0]) return -1;
    else {
        seL4_Word len = MIN(ipc->msg[1], nbyte);
        char *ptr = ipc->msg + 2;
        strncpy(name, ptr, len);
        if (len > 0) {
            // Force null byte
            name[len - 1] = '\0';
        }
        return (int) len;
    }
}

void sos_sys_usleep(int msec) {
    seL4_IPCBuffer *ipc;
    seL4_MessageInfo_t tag;

    if (msec <= 0) return;

    ipc = seL4_GetIPCBuffer();
    tag = seL4_MessageInfo_new(0, 0, 0, 2);
    ipc->msg[0] = SOS_SYSCALL_TIME_USLEEP;
    ipc->msg[1] = (seL4_Word) msec;
    seL4_Call(SOS_IPC_EP_CAP, tag);
}

int64_t sos_sys_time_stamp(void) {
    seL4_IPCBuffer *buf;
    seL4_MessageInfo_t tag;
    int64_t ret;

    buf = seL4_GetIPCBuffer();
    tag = seL4_MessageInfo_new(0, 0, 0, 1);
    // uint64_t takes up two words
    buf->msg[0] = SOS_SYSCALL_TIME_STAMP;
    seL4_Call(SOS_IPC_EP_CAP, tag);

    memcpy(&ret, buf->msg, sizeof(ret));

    return ret;
}

seL4_Word sos_sys_brk(seL4_Word new_brk_vaddr) {
    seL4_IPCBuffer *buf;
    seL4_MessageInfo_t tag;
    buf = seL4_GetIPCBuffer();
    tag = seL4_MessageInfo_new(0, 0, 0, 2);
    buf->msg[0] = SOS_SYSCALL_BRK;
    buf->msg[1] = new_brk_vaddr;
    seL4_Call(SOS_IPC_EP_CAP, tag);

    if (buf->msg[1]) return NULL;
    else return new_brk_vaddr;
}

pid_t sos_process_create(const char *path) {
    seL4_IPCBuffer *ipc;
    seL4_MessageInfo_t tag;
    seL4_Word *data;
    int len;
    int i;

    if (path == NULL) return -1;

    ipc = seL4_GetIPCBuffer();
    len = strnlen(path, SOS_PATH_MAX);
    data = (seL4_Word *) path;
    tag = seL4_MessageInfo_new(0, 0, 0, seL4_MsgMaxLength);
    ipc->msg[0] = SOS_SYSCALL_PROC_CREATE;
    ipc->msg[1] = len;

    /* Copy after syscall ID and length. Include the '\0' byte! */
    *(((char *) (ipc->msg + 2)) + len + 1) = '\0';
    strncpy(ipc->msg + 2, path, len + 1);
    seL4_Call(SOS_IPC_EP_CAP, tag);

    if (ipc->msg[1]) return -1;
    else return (ipc->msg[0]);
}

int sos_process_delete(pid_t pid) {
    if (pid < 0) return -1;

    seL4_IPCBuffer *buf;
    seL4_MessageInfo_t tag;

    buf = seL4_GetIPCBuffer();
    tag = seL4_MessageInfo_new(0, 0, 0, 2);
    buf->msg[0] = SOS_SYSCALL_PROC_DELETE;
    buf->msg[1] = (seL4_Word) pid;
    seL4_Call(SOS_IPC_EP_CAP, tag);

    if (buf->msg[0]) return -1;
    else return 0;
}

pid_t sos_my_id(void) {
    seL4_IPCBuffer *buf;
    seL4_MessageInfo_t tag;

    buf = seL4_GetIPCBuffer();
    tag = seL4_MessageInfo_new(0, 0, 0, 1);
    buf->msg[0] = SOS_SYSCALL_PID;
    seL4_Call(SOS_IPC_EP_CAP, tag);

    return (pid_t) (buf->msg[0]);
}

int sos_process_status(sos_process_t *processes, unsigned max) {
    int err;
    seL4_IPCBuffer *buf;
    seL4_MessageInfo_t tag;

    int64_t ts;

    int i;

    if (processes == NULL) return 0;

    buf = seL4_GetIPCBuffer();
    tag = seL4_MessageInfo_new(0, 0, 0, 2);

    // In a loop, send a request for the nth process up to max
    for (i = 0; i < max; i++) {
        buf->msg[0] = SOS_SYSCALL_PROC_STATUS;
        buf->msg[1] = (seL4_Word) i;
        seL4_Call(SOS_IPC_EP_CAP, tag);

        if (buf->msg[0]) {
            // Finished
            break;
        }

        processes[i].pid = (int) buf->msg[1];
        processes[i].size = (unsigned) buf->msg[2];
        memcpy(&ts, &(buf->msg[3]), sizeof(ts));
        processes[i].stime = (ts / 1000);
        strncpy(processes[i].command, &(buf->msg[5]), N_NAME);

    }

    return i;
}

pid_t sos_process_wait(pid_t pid) {
    if (pid < -1) return -1;

    // Use 0 == arbitrary, to avoid weird casting errors
    if (pid == -1) pid = 0;

    seL4_IPCBuffer *buf;
    seL4_MessageInfo_t tag;

    buf = seL4_GetIPCBuffer();
    tag = seL4_MessageInfo_new(0, 0, 0, 2);
    buf->msg[0] = SOS_SYSCALL_PROC_WAIT;
    buf->msg[1] = (seL4_Word) pid;
    seL4_Call(SOS_IPC_EP_CAP, tag);

    if (buf->msg[0]) return -1;
    else return ((int) (buf->msg[1]));
}
